<?php
namespace adminmatrix\admin\listen;

use think\App;
use adminmatrix\admin\listen\annotation\Group;
use adminmatrix\admin\listen\annotation\Title;
use adminmatrix\admin\listen\annotation\Event;
use ReflectionClass;
use ReflectionMethod;

/**
 * 事件和监听器管理类
 */
class Listen
{
    /**
     * 应用实例
     * @var App
     */
    protected $app;
    
    /**
     * 要读取的文件夹
     * @var array
     */
    protected array $type = ['event', 'listener' ,'subscribe'];
    
    /**
     * 需要扫描注解的目录
     * @var array
     */
    protected array $annotationDirs = ['controller', 'model', 'lists', 'logic'];
    
    /**
     * 事件列表
     * @var array
     */
    protected array $eventList = [];
    
    /**
     * 监听器列表
     * @var array
     */
    protected array $listenList = [];
    
    /**
     * 订阅者列表
     * @var array
     */
    protected array $subscribeList = [];
    
    /**
     * 注解事件列表
     * @var array
     */
    protected array $annotationEventList = [];
    
    /**
     * 构造函数
     * @param App $app 应用实例
     */
    public function __construct(App $app)
    {

        $this->app = $app;
        $this->scanEvents();
        $this->scanAnnotationEvents();
    }
    
    /**
     * 扫描所有事件和监听器
     */
    protected function scanEvents(): void
    {
        $app = $this->app->getAppPath();
        
        // 获取应用目录下的所有子目录
        $appDirs = glob($app . '*', GLOB_ONLYDIR);
        $searchPaths = [''];  // 默认包含应用根目录
        
        // 添加所有子目录作为搜索路径
        foreach ($appDirs as $dir) {
            $searchPaths[] = basename($dir) . '/';
        }
        
        // 在所有搜索路径中查找指定类型的目录
        foreach ($searchPaths as $searchPath) {
            foreach ($this->type as $directory) {
                $path = $app . $searchPath . $directory;
                if (is_dir($path)) {
                    // 根据目录类型将文件分别添加到对应的数组中
                    if ($directory === 'event') {
                        $this->scanDirectory($path, $this->eventList, $directory, $searchPath);
                    } elseif ($directory === 'listener') {
                        $this->scanDirectory($path, $this->listenList, $directory, $searchPath);
                    } elseif ($directory === 'subscribe') {
                        $this->scanDirectory($path, $this->subscribeList, $directory, $searchPath);
                    }
                }
            }
        }
    }
    
    /**
     * 扫描注解事件
     */
    protected function scanAnnotationEvents(): void
    {
        $app = $this->app->getAppPath();
        
        // 获取应用目录下的所有子目录
        $appDirs = glob($app . '*', GLOB_ONLYDIR);
        $searchPaths = [''];  // 默认包含应用根目录
        
        // 添加所有子目录作为搜索路径
        foreach ($appDirs as $dir) {
            $searchPaths[] = basename($dir) . '/';
        }
        
        // 在所有搜索路径中查找指定类型的目录
        foreach ($searchPaths as $searchPath) {
            foreach ($this->annotationDirs as $directory) {
                $path = $app . $searchPath . $directory;
                if (is_dir($path)) {
                    $this->scanAnnotationDirectory($path, $searchPath, $directory);
                }
            }
        }
    }
    
    /**
     * 扫描目录中的注解事件
     * @param string $directory 目录路径
     * @param string $searchPath 搜索路径
     * @param string $dirType 目录类型
     */
    protected function scanAnnotationDirectory(string $directory, string $searchPath, string $dirType): void
    {
        $items = glob($directory . '/*');
        
        foreach ($items as $item) {
            if (is_dir($item)) {
                // 递归扫描子目录
                $this->scanAnnotationDirectory($item, $searchPath, $dirType);
            } elseif (pathinfo($item, PATHINFO_EXTENSION) === 'php') {
                // 处理PHP文件
                $className = basename($item, '.php');
                $namespace = $this->getNamespaceFromFile($item);
                $fullClassName = $namespace . '\\' . $className;
                
                try {
                    if (class_exists($fullClassName)) {
                        $this->processClassAnnotations($fullClassName, $dirType);
                    }
                } catch (\Throwable $e) {
                    // 处理类失败，忽略
                }
            }
        }
    }
    
    /**
     * 处理类的注解
     * @param string $className 完整类名
     * @param string $dirType 目录类型
     */
    protected function processClassAnnotations(string $className, string $dirType): void
    {
        try {
            $reflector = new ReflectionClass($className);
            
            // 处理类级别的Event注解
            $eventAttributes = $reflector->getAttributes(Event::class);
            foreach ($eventAttributes as $attribute) {
                $event = $attribute->newInstance();
                $this->registerAnnotationEvent($event, $className, '', $dirType);
            }
            
            // 处理方法级别的Event注解
            $methods = $reflector->getMethods(ReflectionMethod::IS_PUBLIC);
            foreach ($methods as $method) {
                $methodEventAttributes = $method->getAttributes(Event::class);
                foreach ($methodEventAttributes as $attribute) {
                    $event = $attribute->newInstance();
                    $this->registerAnnotationEvent($event, $className, $method->getName(), $dirType);
                }
            }
        } catch (\Throwable $e) {
            // 处理注解失败，忽略
        }
    }
    
    /**
     * 注册注解事件
     * @param Event $event 事件注解
     * @param string $className 类名
     * @param string $methodName 方法名
     * @param string $dirType 目录类型
     */
    protected function registerAnnotationEvent(Event $event, string $className, string $methodName, string $dirType): void
    {
        $eventName = $event->name;
        $eventTitle = $event->title;
        $eventRef = $event->ref;
        
        // 构建事件处理器
        $handler = $methodName ? [$className, $methodName] : $className;
        
        // 存储注解事件信息
        $this->annotationEventList[] = [
            'name' => $eventName,
            'title' => $eventTitle,
            'ref' => $eventRef,
            'class' => $className,
            'method' => $methodName,
            'type' => $dirType,
            'handler' => $handler
        ];
    }
    
    /**
     * 递归扫描目录获取所有PHP文件
     * @param string $directory 目录路径
     * @param array &$files 收集的文件数组
     * @param string $baseType 基础类型（event或listener）
     * @param string $searchPath 搜索路径前缀
     * @return void
     */
    protected function scanDirectory(string $directory, array &$files, string $baseType = '', string $searchPath = ''): void
    {
        $items = glob($directory . '/*');
        
        foreach ($items as $item) {
            if (is_dir($item)) {
                // 递归扫描子目录
                $this->scanDirectory($item, $files, $baseType, $searchPath);
            } elseif (pathinfo($item, PATHINFO_EXTENSION) === 'php') {
                // 处理PHP文件
                $className = basename($item, '.php');
                $namespace = $this->getNamespaceFromFile($item);
                $fullClassName = $namespace . '\\' . $className;
                
                // 获取相对目录路径，用于生成默认分组名
                $relativePath = str_replace($this->app->getAppPath() . $searchPath . $baseType . '/', '', dirname($item));
                $defaultGroup = $relativePath ? ucfirst($relativePath) : ucfirst($baseType);
                
                // 生成默认标题
                $defaultTitle = $defaultGroup . $className;
                
                // 尝试加载类并解析注解
                $groupName = $defaultGroup;
                $titleName = $defaultTitle;
                $methods = [];
                
                try {
                    // 如果是订阅者，直接从文件中解析方法
                    if ($baseType === 'subscribe') {
                        $methods = $this->getMethodsFromFile($item);
                        trace('从文件解析订阅者 ' . $className . ' 的方法，找到 ' . count($methods) . ' 个方法', 'info');
                    }
                    
                    // 尝试通过反射获取更多信息
                    if (class_exists($fullClassName)) {
                        $reflector = new ReflectionClass($fullClassName);
                        
                        // 解析Group注解
                        $groupAttributes = $reflector->getAttributes(Group::class);
                        if (!empty($groupAttributes)) {
                            $groupInstance = $groupAttributes[0]->newInstance();
                            $groupName = $groupInstance->group;
                        }
                        
                        // 解析Title注解
                        $titleAttributes = $reflector->getAttributes(Title::class);
                        if (!empty($titleAttributes)) {
                            $titleInstance = $titleAttributes[0]->newInstance();
                            $titleName = $titleInstance->title;
                        }
                        
                        // 如果是订阅者，并且还没有通过文件解析获取到方法，尝试通过反射获取
                        if ($baseType === 'subscribe' && empty($methods)) {
                            $classMethods = $reflector->getMethods(ReflectionMethod::IS_PUBLIC);
                            foreach ($classMethods as $method) {
                                $methodName = $method->getName();
                                // 排除subscribe方法和静态方法，确保方法是在当前类中声明的
                                if ($methodName !== 'subscribe' && !$method->isStatic() && $method->getDeclaringClass()->getName() === $fullClassName) {
                                    $methodTitle = $methodName;
                                    
                                    // 检查方法是否有Title注解
                                    $methodTitleAttributes = $method->getAttributes(Title::class);
                                    if (!empty($methodTitleAttributes)) {
                                        $methodTitleInstance = $methodTitleAttributes[0]->newInstance();
                                        $methodTitle = $methodTitleInstance->title;
                                    }
                                    
                                    $methods[] = [
                                        'name' => $methodName,
                                        'title' => $methodTitle,
                                        'parameters' => $method->getNumberOfParameters()
                                    ];
                                }
                            }
                            
                            trace('通过反射解析订阅者 ' . $className . ' 的方法，找到 ' . count($methods) . ' 个方法', 'info');
                        }
                    }
                } catch (\Throwable $e) {
                    // 解析注解失败，使用默认值
                    trace('解析类失败: ' . $fullClassName . ' - ' . $e->getMessage(), 'error');
                }
                
                $fileInfo = [
                    'group' => $groupName,
                    'name' => $className,
                    'title' => $titleName,
                    'path' => $item,
                    'namespace' => $namespace,
                    'class' => $fullClassName,
                    'type' => $baseType
                ];
                
                // 如果是订阅者，添加方法信息
                if ($baseType === 'subscribe') {
                    $fileInfo['methods'] = $methods;
                    // 记录调试信息
                    trace('添加订阅者: ' . $className . ', 方法数量: ' . count($methods), 'info');
                }
                
                $files[] = $fileInfo;
            }
        }
    }
    
    /**
     * 从文件中直接解析方法
     * @param string $file 文件路径
     * @return array 方法信息数组
     */
    protected function getMethodsFromFile(string $file): array
    {
        $methods = [];
        $content = file_get_contents($file);
        
        // 首先尝试找出所有的方法
        if (preg_match_all('/public\s+function\s+(\w+)\s*\(([^\)]*)\)/i', $content, $methodMatches, PREG_SET_ORDER)) {
            foreach ($methodMatches as $match) {
                $methodName = $match[1];
                
                // 排除subscribe方法
                if ($methodName !== 'subscribe') {
                    // 默认使用方法名作为标题
                    $methodTitle = $methodName;
                    
                    // 尝试查找方法的Title注解
                    $titlePattern = '/#\[Title\(["\']([^"\']+)["\']\)\]\s*(?:\/\*[\s\S]*?\*\/\s*)?public\s+function\s+' . preg_quote($methodName, '/') . '\s*\(/i';
                    if (preg_match($titlePattern, $content, $titleMatch)) {
                        $methodTitle = $titleMatch[1];
                    }
                    
                    // 计算参数数量
                    $params = trim($match[2]);
                    $paramCount = empty($params) ? 0 : count(explode(',', $params));
                    
                    $methods[] = [
                        'name' => $methodName,
                        'title' => $methodTitle,
                        'parameters' => $paramCount
                    ];
                }
            }
        }
        
        // 记录调试信息
        trace('文件 ' . basename($file) . ' 中找到 ' . count($methods) . ' 个方法', 'info');
        
        return $methods;
    }
    
    /**
     * 从文件中提取命名空间
     * @param string $file 文件路径
     * @return string|null
     */
    protected function getNamespaceFromFile(string $file): ?string
    {
        $content = file_get_contents($file);
        if (preg_match('/namespace\s+([^;]+);/', $content, $matches)) {
            return $matches[1];
        }
        return null;
    }
    
    /**
     * 获取事件或监听器信息
     * @param string $type 类型：event、listener或subscribe，为空则获取全部
     * @param string $name 事件名称，为空则获取全部
     * @return array
     */
    public function get(string $type = '', string $name = ''): array
    {
        // 根据类型过滤
        if ($type === 'event') {
            $result = $this->eventList;
        } elseif ($type === 'listener') {
            $result = $this->listenList;
        } elseif ($type === 'subscribe') {
            $result = $this->subscribeList;
        } elseif ($type === 'annotation') {
            $result = $this->annotationEventList;
        } else {
            $result = [
                'event' => $this->eventList,
                'listener' => $this->listenList,
                'subscribe' => $this->subscribeList,
                'annotation' => $this->annotationEventList
            ];
        }
        
        // 根据名称过滤
        if (!empty($name)) {
            if (in_array($type, ['event', 'listener', 'subscribe', 'annotation'])) {
                $result = array_filter($result, function($item) use ($name) {
                    return $item['name'] === $name;
                });
            } else {
                // 在所有数组中分别过滤
                foreach (['event', 'listener', 'subscribe', 'annotation'] as $t) {
                    if (isset($result[$t])) {
                        $result[$t] = array_filter($result[$t], function($item) use ($name) {
                            return $item['name'] === $name;
                        });
                    }
                }
            }
        }
        
        return $result;
    }
    
    /**
     * 按分组获取事件或监听器
     * @param string $type 类型：event、listener或subscribe，为空则获取全部
     * @return array
     */
    public function getByGroup(string $type = ''): array
    {
        $data = $this->get($type);
        $result = [];
        
        $processGroup = function($items, $type) use (&$result) {
            $groups = [];
            
            foreach ($items as $item) {
                $group = $item['group'] ?? ($item['type'] ?? 'Unknown');
                if (!isset($groups[$group])) {
                    $groups[$group] = [];
                }
                $groups[$group][] = $item;
            }
            
            if (in_array($type, ['event', 'listener', 'subscribe', 'annotation'])) {
                $result = $groups;
            } else {
                $result[$type] = $groups;
            }
        };
        
        if ($type === 'event' || empty($type)) {
            $processGroup($type === 'event' ? $data : $data['event'], 'event');
        }
        
        if ($type === 'listener' || empty($type)) {
            $processGroup($type === 'listener' ? $data : $data['listener'], 'listener');
        }
        
        if ($type === 'subscribe' || empty($type)) {
            $processGroup($type === 'subscribe' ? $data : $data['subscribe'], 'subscribe');
        }
        
        if ($type === 'annotation' || empty($type)) {
            $processGroup($type === 'annotation' ? $data : $data['annotation'], 'annotation');
        }
        
        return $result;
    }
    
    /**
     * 获取已注册的事件监听关系
     * @return array
     */
    public function getRegisteredEvents(): array
    {
        $event = $this->app->event;
        
        // 通过反射获取事件实例中的监听器列表
        $reflection = new \ReflectionClass($event);
        $property = $reflection->getProperty('listener');
        $property->setAccessible(true);
        
        return $property->getValue($event);
    }
    
    /**
     * 获取事件列表
     * @return array
     */
    public function getEventList(): array
    {
        return $this->eventList;
    }
    
    /**
     * 获取监听器列表
     * @return array
     */
    public function getListenList(): array
    {
        return $this->listenList;
    }
    
    /**
     * 获取订阅者列表
     * @return array
     */
    public function getSubscribeList(): array
    {
        return $this->subscribeList;
    }
    
    /**
     * 获取注解事件列表
     * @return array
     */
    public function getAnnotationEventList(): array
    {
        return $this->annotationEventList;
    }
    
    /**
     * 注册所有事件和监听器
     */
    public function registerAll(): void
    {
        # 注册 event 事件
        foreach ($this->eventList as $event) {
            $this->app->event->listen($event['name'], $event['class']);
        }

        # 注册 listener 事件
        foreach ($this->listenList as $listen) {
            # 注册事件
            $this->app->event->listen($listen['name'], $listen['class']);
            # 注册批量通配事件
            $this->app->event->listen($listen['name'] . '.*', $listen['class']);
        }
        
        # 注册 subscribe 订阅者
        foreach ($this->subscribeList as $subscribe) {
            $this->app->event->subscribe($subscribe['class']);
        }
        
        # 注册注解事件
        foreach ($this->annotationEventList as $annotationEvent) {
            $eventName = $annotationEvent['name'];
            $handler = $annotationEvent['handler'];
            
            # 注册事件
            $this->app->event->listen($eventName, $handler);
            
            # 如果有动态引用，则注册动态事件
            if (!empty($annotationEvent['ref'])) {
                $refName = $annotationEvent['ref'];
                $this->app->event->listen($refName . '.*', $handler);
            }
        }
    }
} 